#ifndef FRAME_H
#define FRAME_H

// res = vec * alpha
#define VEC3_AMS( res, vec, alpha ) \
		( res ).x = ( vec ).x * ( alpha ), \
		( res ).y = ( vec ).y * ( alpha ), \
		( res ).z = ( vec ).z * ( alpha )

namespace cg
{
	class Frame
	{
	public:
						Frame();

		const Vec3 &	GetForward( void ) const;
		Vec3 &			GetForward( void );
		const Vec3 &	GetOrigin( void ) const;
		Vec3 &			GetOrigin( void );
		const Vec3 &	GetUp( void ) const;
		Vec3 &			GetUp( void );

		void			SetForward( const float x, const float y, const float z );
		void			SetOrigin( const float x, const float y, const float z );
		void			SetUp( const float x, const float y, const float z );		
		void			SetForward( const Vec3 &forward );
		void			SetOrigin( const Vec3 &origin );
		void			SetUp( const Vec3 &up );	

		void			MoveForward( const float delta );
		void			MoveRight( const float delta );
		void			MoveUp( const float delta );

		void			Yaw( const float delta );

	private:
		Vec3			forward;
		Vec3			up;
		Vec3			origin;
	};

	CG_INLINE Frame::Frame()
	{
		forward.Set( 0, 0, 1 );		
		origin.Set( 0, 0, 0 );		
		up.Set( 0, 1, 0 );
	}

	CG_INLINE const Vec3 & Frame::GetForward( void ) const
	{
		return forward;
	}

	CG_INLINE Vec3 & Frame::GetForward( void )
	{
		return forward;
	}

	CG_INLINE const Vec3 & Frame::GetOrigin( void ) const
	{
		return origin;
	}

	CG_INLINE Vec3 & Frame::GetOrigin( void )
	{
		return origin;
	}

	CG_INLINE const Vec3 & Frame::GetUp( void ) const
	{
		return up;
	}

	CG_INLINE Vec3 & Frame::GetUp( void ) 
	{
		return up;
	}

	CG_INLINE void Frame::SetForward( const float x, const float y, const float z )
	{
		forward.Set( x, y, z );				
	}

	CG_INLINE void Frame::SetForward( const Vec3 &forward )
	{
		this->forward  = forward;
	}

	CG_INLINE void Frame::SetOrigin( const float x, const float y, const float z )
	{
		origin.Set( x, y, z );		
	}

	CG_INLINE void Frame::SetOrigin( const Vec3 &origin )
	{
		this->origin = origin;
	}

	CG_INLINE void Frame::SetUp( const float x, const float y, const float z )
	{
		up.Set( x, y, z );		
	}

	CG_INLINE void Frame::SetUp( const Vec3 &up )
	{
		this->up = up;		
	}

	CG_INLINE void Frame::MoveForward( const float delta )
	{
		//VEC3_AMS ( origin, forward, delta ); 
		origin.x += forward.x * delta;
		origin.y += forward.y * delta;
		origin.z += forward.z * delta;
	}

	CG_INLINE void Frame::MoveRight( const float delta )
	{
		Vec3 right = up.Cross( forward );
		origin.x += right.x * delta;
		origin.y += right.y * delta;
		origin.z += right.z * delta;
	}

	CG_INLINE void Frame::MoveUp( const float delta )
	{		
		origin.x += up.x * delta;
		origin.y += up.y * delta;
		origin.z += up.z * delta;
	}
	
	CG_INLINE void Frame::Yaw( const float delta )
	{		
		forward = ( RotationMatrix( delta, up ) * toVec4( forward ) ).toVec3();			
	}

	///////////////////////////////////////////////////////////////////////////

	// helping function - to keep public interface small

	///////////////////////////////////////////////////////////////////////////

	CG_INLINE void SetForward( Frame &frame, const Vec3 &v )
	{
		frame.SetForward( v.x, v.y, v.z );
	}

	CG_INLINE void SetOrigin( Frame &frame, const Vec3 &v )
	{
		frame.SetOrigin( v.x, v.y, v.z );
	}

	CG_INLINE void SetUp( Frame &frame, const Vec3 &v )
	{
		frame.SetUp( v.x, v.y, v.z );
	}

	CG_INLINE void GetMatrix( const Frame& frame, Mat4* m, bool rotationOnly = false )
	{
		assert(m);
		// find out right
		Vec3 right = Cross( frame.GetUp(), frame.GetForward() );
		// set columns		
		( *m )[ 0 ] = toVec4( right );
		( *m )[ 1 ] = toVec4( frame.GetUp() );
		( *m )[ 2 ] = toVec4( frame.GetForward() );
		// 
		if ( rotationOnly ) { // null translation
			( *m )[ 3 ] = Vec4::ZERO;
		} else {
			( *m )[ 3 ] = toVec4( frame.GetOrigin() );
		}
		( *m )[ 3 ].w = 1.0f;
	}

	CG_INLINE Mat4 GetMatrix( const Frame& frame, bool rotationOnly = false )
	{
		Mat4 m;

		GetMatrix( frame, &m, rotationOnly );

		return m;
	}
}

#endif